//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef _AST_H_
#define _AST_H_

#include "evalState.h"
#include "platform/types.h"

class SimObject;
class SimGroup;
class CodeStream;

/// Enable this #define if you are seeing the message "precompile size mismatch" in the console.
/// This will help track down which node type is causing the error. It could be
/// due to incorrect compiler optimization.
//#define DEBUG_AST_NODES

enum TypeReq
{
   TypeReqNone,
   TypeReqUInt,
   TypeReqFloat,
   TypeReqString
};

enum ExprNodeName
{
   NameExprNode,
   NameFloatNode,
   NameIntNode,
   NameVarNode
};

/// Representation of a node for the scripting language parser.
///
/// When the scripting language is evaluated, it is turned from a string representation,
/// into a parse tree, thence into byte code, which is ultimately interpreted by the VM.
///
/// This is the base class for the nodes in the parse tree. There are a great many subclasses,
/// each representing a different language construct.
struct StmtNode
{
   StmtNode* next;   ///< Next entry in parse tree.

   StmtNode();
   virtual ~StmtNode() {}

   /// @name next Accessors
   /// @{

   ///
   void append(StmtNode* next);
   StmtNode* getNext() const { return next; }

   /// @}

   /// @name Debug Info
   /// @{

   StringTableEntry dbgFileName; ///< Name of file this node is associated with.
   S32 dbgLineNumber;            ///< Line number this node is associated with.
#ifdef DEBUG_AST_NODES
   virtual String dbgStmtType() const = 0;
#endif
   /// @}

   /// @name Breaking
   /// @{

   void addBreakLine(CodeStream& codeStream);
   /// @}

   /// @name Compilation
   /// @{

   virtual U32 compileStmt(CodeStream& codeStream, U32 ip) = 0;
   virtual void setPackage(StringTableEntry packageName);
   /// @}
};

/// Helper macro
#ifndef DEBUG_AST_NODES
#  define DBG_STMT_TYPE(s) virtual const char* dbgStmtType() const { return "#s"; }
#else
#  define DBG_STMT_TYPE(s)
#endif

struct BreakStmtNode : StmtNode
{
   static BreakStmtNode* alloc(S32 lineNumber);


   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   DBG_STMT_TYPE(BreakStmtNode);
};

struct ContinueStmtNode : StmtNode
{
   static ContinueStmtNode* alloc(S32 lineNumber);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   DBG_STMT_TYPE(ContinueStmtNode);
};

/// A mathematical expression.
struct ExprNode : StmtNode
{
   ExprNode* optimizedNode;

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;

   virtual U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) = 0;
   virtual TypeReq getPreferredType() = 0;
   virtual ExprNodeName getExprNodeNameEnum() const { return NameExprNode; }
};

struct ReturnStmtNode : StmtNode
{
   ExprNode* expr;

   static ReturnStmtNode* alloc(S32 lineNumber, ExprNode* expr);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   DBG_STMT_TYPE(ReturnStmtNode);
};

struct IfStmtNode : StmtNode
{
   ExprNode* testExpr;
   StmtNode* ifBlock, * elseBlock;
   U32 endifOffset;
   U32 elseOffset;
   bool integer;
   bool propagate;

   static IfStmtNode* alloc(S32 lineNumber, ExprNode* testExpr, StmtNode* ifBlock, StmtNode* elseBlock, bool propagateThrough);
   void propagateSwitchExpr(ExprNode* left, bool string);
   ExprNode* getSwitchOR(ExprNode* left, ExprNode* list, bool string);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   DBG_STMT_TYPE(IfStmtNode);
};

struct LoopStmtNode : StmtNode
{
   ExprNode* testExpr;
   ExprNode* initExpr;
   ExprNode* endLoopExpr;
   StmtNode* loopBlock;
   bool isDoLoop;
   U32 breakOffset;
   U32 continueOffset;
   U32 loopBlockStartOffset;
   bool integer;

   static LoopStmtNode* alloc(S32 lineNumber, ExprNode* testExpr, ExprNode* initExpr, ExprNode* endLoopExpr, StmtNode* loopBlock, bool isDoLoop);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   DBG_STMT_TYPE(LoopStmtNode);
};

/// A "foreach" statement.
struct IterStmtNode : StmtNode
{
   /// Local variable name to use for the container element.
   StringTableEntry varName;

   /// Expression evaluating to a SimSet object.
   ExprNode* containerExpr;

   /// The statement body.
   StmtNode* body;

   /// If true, this is a 'foreach$'.
   bool isStringIter;

   /// Bytecode size of body statement.  Set by precompileStmt.
   U32 bodySize;

   static IterStmtNode* alloc(S32 lineNumber, StringTableEntry varName, ExprNode* containerExpr, StmtNode* body, bool isStringIter);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
};

/// A binary mathematical expression (ie, left op right).
struct BinaryExprNode : ExprNode
{
   S32 op;
   ExprNode* left;
   ExprNode* right;
};

struct FloatBinaryExprNode : BinaryExprNode
{
   static FloatBinaryExprNode* alloc(S32 lineNumber, S32 op, ExprNode* left, ExprNode* right);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;

   bool optimize();

   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(FloatBinaryExprNode);
};

struct ConditionalExprNode : ExprNode
{
   ExprNode* testExpr;
   ExprNode* trueExpr;
   ExprNode* falseExpr;
   bool integer;
   static ConditionalExprNode* alloc(S32 lineNumber, ExprNode* testExpr, ExprNode* trueExpr, ExprNode* falseExpr);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(ConditionalExprNode);
};

struct IntBinaryExprNode : BinaryExprNode
{
   TypeReq subType;
   U32 operand;

   static IntBinaryExprNode* alloc(S32 lineNumber, S32 op, ExprNode* left, ExprNode* right);

   void getSubTypeOperand();

   bool optimize();

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(IntBinaryExprNode);
};

struct StreqExprNode : BinaryExprNode
{
   bool eq;
   static StreqExprNode* alloc(S32 lineNumber, ExprNode* left, ExprNode* right, bool eq);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(StreqExprNode);
};

struct StrcatExprNode : BinaryExprNode
{
   S32 appendChar;
   static StrcatExprNode* alloc(S32 lineNumber, ExprNode* left, ExprNode* right, S32 appendChar);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(StrcatExprNode);
};

struct CommaCatExprNode : BinaryExprNode
{
   static CommaCatExprNode* alloc(S32 lineNumber, ExprNode* left, ExprNode* right);


   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(CommaCatExprNode);
};

struct IntUnaryExprNode : ExprNode
{
   S32 op;
   ExprNode* expr;
   bool integer;

   static IntUnaryExprNode* alloc(S32 lineNumber, S32 op, ExprNode* expr);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(IntUnaryExprNode);
};

struct FloatUnaryExprNode : ExprNode
{
   S32 op;
   ExprNode* expr;

   static FloatUnaryExprNode* alloc(S32 lineNumber, S32 op, ExprNode* expr);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(FloatUnaryExprNode);
};

struct VarNode : ExprNode
{
   StringTableEntry varName;
   ExprNode* arrayIndex;

   ExprNode* defaultValue;   // optional expression

   static VarNode* alloc(S32 lineNumber, StringTableEntry varName, ExprNode* arrayIndex);

   // function params initialization.
   static VarNode* allocParam(S32 lineNumber, StringTableEntry varName, ExprNode* defaultValue);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   ExprNodeName getExprNodeNameEnum() const override { return NameVarNode; }
   DBG_STMT_TYPE(VarNode);
};

struct IntNode : ExprNode
{
   S32 value;
   U32 index; // if it's converted to float/string

   static IntNode* alloc(S32 lineNumber, S32 value);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   ExprNodeName getExprNodeNameEnum() const override { return NameIntNode; }
   DBG_STMT_TYPE(IntNode);
};

struct FloatNode : ExprNode
{
   F64 value;
   U32 index;

   static FloatNode* alloc(S32 lineNumber, F64 value);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   ExprNodeName getExprNodeNameEnum() const override { return NameFloatNode; }
   DBG_STMT_TYPE(FloatNode);
};

struct StrConstNode : ExprNode
{
   char* str;
   F64 fVal;
   U32 index;
   bool tag;
   bool doc; // Specifies that this string is a documentation block.

   static StrConstNode* alloc(S32 lineNumber, const char* str, bool tag, bool doc = false);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(StrConstNode);
};

struct ConstantNode : ExprNode
{
   StringTableEntry value;
   F64 fVal;
   U32 index;

   static ConstantNode* alloc(S32 lineNumber, StringTableEntry value);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(ConstantNode);
};

struct AssignExprNode : ExprNode
{
   StringTableEntry varName;
   ExprNode* expr;
   ExprNode* arrayIndex;
   TypeReq subType;

   static AssignExprNode* alloc(S32 lineNumber, StringTableEntry varName, ExprNode* arrayIndex, ExprNode* expr);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(AssignExprNode);
};

struct AssignDecl
{
   S32 lineNumber;
   S32 token;
   ExprNode* expr;
   bool integer;
};

struct AssignOpExprNode : ExprNode
{
   StringTableEntry varName;
   ExprNode* expr;
   ExprNode* arrayIndex;
   S32 op;
   U32 operand;
   TypeReq subType;

   static AssignOpExprNode* alloc(S32 lineNumber, StringTableEntry varName, ExprNode* arrayIndex, ExprNode* expr, S32 op);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(AssignOpExprNode);
};

struct TTagSetStmtNode : StmtNode
{
   StringTableEntry tag;
   ExprNode* valueExpr;
   ExprNode* stringExpr;

   static TTagSetStmtNode* alloc(S32 lineNumber, StringTableEntry tag, ExprNode* valueExpr, ExprNode* stringExpr);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   DBG_STMT_TYPE(TTagSetStmtNode);
};

struct TTagDerefNode : ExprNode
{
   ExprNode* expr;

   static TTagDerefNode* alloc(S32 lineNumber, ExprNode* expr);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(TTagDerefNode);
};

struct TTagExprNode : ExprNode
{
   StringTableEntry tag;

   static TTagExprNode* alloc(S32 lineNumber, StringTableEntry tag);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(TTagExprNode);
};

struct FuncCallExprNode : ExprNode
{
   StringTableEntry funcName;
   StringTableEntry nameSpace;
   ExprNode* args;
   U32 callType;
   enum {
      FunctionCall,
      StaticCall,
      MethodCall,
      ParentCall
   };

   static FuncCallExprNode* alloc(S32 lineNumber, StringTableEntry funcName, StringTableEntry nameSpace, ExprNode* args, bool dot);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(FuncCallExprNode);
};

struct AssertCallExprNode : ExprNode
{
   ExprNode* testExpr;
   const char* message;
   U32 messageIndex;

   static AssertCallExprNode* alloc(S32 lineNumber, ExprNode* testExpr, const char* message);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(AssertCallExprNode);
};

struct SlotDecl
{
   S32              lineNumber;
   ExprNode* object;
   StringTableEntry slotName;
   ExprNode* array;
};

struct SlotAccessNode : ExprNode
{
   ExprNode* objectExpr, * arrayExpr;
   StringTableEntry slotName;

   static SlotAccessNode* alloc(S32 lineNumber, ExprNode* objectExpr, ExprNode* arrayExpr, StringTableEntry slotName);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(SlotAccessNode);
};

struct InternalSlotDecl
{
   S32              lineNumber;
   ExprNode* object;
   ExprNode* slotExpr;
   bool             recurse;
};

struct InternalSlotAccessNode : ExprNode
{
   ExprNode* objectExpr, * slotExpr;
   bool recurse;

   static InternalSlotAccessNode* alloc(S32 lineNumber, ExprNode* objectExpr, ExprNode* slotExpr, bool recurse);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(InternalSlotAccessNode);
};

struct SlotAssignNode : ExprNode
{
   ExprNode* objectExpr, * arrayExpr;
   StringTableEntry slotName;
   ExprNode* valueExpr;
   U32 typeID;

   static SlotAssignNode* alloc(S32 lineNumber, ExprNode* objectExpr, ExprNode* arrayExpr, StringTableEntry slotName, ExprNode* valueExpr, U32 typeID = -1);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(SlotAssignNode);
};

struct SlotAssignOpNode : ExprNode
{
   ExprNode* objectExpr, * arrayExpr;
   StringTableEntry slotName;
   S32 op;
   ExprNode* valueExpr;
   U32 operand;
   TypeReq subType;

   static SlotAssignOpNode* alloc(S32 lineNumber, ExprNode* objectExpr, StringTableEntry slotName, ExprNode* arrayExpr, S32 op, ExprNode* valueExpr);

   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(SlotAssignOpNode);
};

struct ObjectDeclNode : ExprNode
{
   ExprNode* classNameExpr;
   StringTableEntry parentObject;
   ExprNode* objectNameExpr;
   ExprNode* argList;
   SlotAssignNode* slotDecls;
   ObjectDeclNode* subObjects;
   bool isDatablock;
   U32 failOffset;
   bool isClassNameInternal;
   bool isSingleton;

   static ObjectDeclNode* alloc(S32 lineNumber, ExprNode* classNameExpr, ExprNode* objectNameExpr, ExprNode* argList, StringTableEntry parentObject, SlotAssignNode* slotDecls, ObjectDeclNode* subObjects, bool isDatablock, bool classNameInternal, bool isSingleton);

   U32 precompileSubObject(bool);
   U32 compile(CodeStream& codeStream, U32 ip, TypeReq type) override;
   U32 compileSubObject(CodeStream& codeStream, U32 ip, bool);
   TypeReq getPreferredType() override;
   DBG_STMT_TYPE(ObjectDeclNode);
};

struct ObjectBlockDecl
{
   SlotAssignNode* slots;
   ObjectDeclNode* decls;
};

struct FunctionDeclStmtNode : StmtNode
{
   StringTableEntry fnName;
   VarNode* args;
   StmtNode* stmts;
   StringTableEntry nameSpace;
   StringTableEntry package;
   U32 endOffset;
   U32 argc;

   static FunctionDeclStmtNode* alloc(S32 lineNumber, StringTableEntry fnName, StringTableEntry nameSpace, VarNode* args, StmtNode* stmts);

   U32 compileStmt(CodeStream& codeStream, U32 ip) override;
   void setPackage(StringTableEntry packageName) override;
   DBG_STMT_TYPE(FunctionDeclStmtNode);
};

namespace Script
{
   inline ExprEvalState gEvalState;

   inline StmtNode *gStatementList;
   inline StmtNode *gAnonFunctionList;
   inline U32 gAnonFunctionID = 0;
}


#endif
